问题的由来 在讨论这个话题之前, 先回顾下之前第二节-如何将上传的 pdf 显示在 confluence 页面中的最后一个请求, 更新 drafts.看下其请求数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 { "status":"current", "title":"测试文档", "space":{ "key":"SPC" }, "body":{ "editor":{ "value":"<p><img class="editor-inline-macro" height="250" src="http://localhost:8090/rest/documentConversion/latest/conversion/thumbnail/4849682/1?attachmentId=4849682&version=1&mimeType=application%2Fpdf&height=250&thumbnailStatus=200" data-macro-name="view-file" data-macro-parameters="height=250|name=测试xxx.pdf" data-macro-schema-version="1" /></p>", "representation":"editor", "content":{ "id":"2818258" } } }, "id":"2818258", "type":"page", "version":{ "number":18, "message":"", "minorEdit":false, "syncRev":"0.AjEmNKvqWOSCexKgWizfyQ0.3" }, "ancestors":[ { "id":"65584", "type":"page" } ] }
需要关注是 body
部分.其中有一个 editor
节点.因为之前在导入 confluence 相关 jar 的时候, 发现有一个类 ContentRepresentation
:
可以看到里面定义了一系列的动作, raw
、storage
、editor
等.所以再看看上面的请求, 觉得这个 body.editor
中的 editor
应该不是一个静态的值, 而是动态的, 也就是说如果是 storage
相关的动作, 那么请求可能是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { "status":"current", "title":"测试文档", "space":{ "key":"SPC" }, "body":{ "storage":{ "value":"...", "representation":"storage", "content":{ "id":"2818258" } } } }
下图进行了下对比:
那么究竟是不是这样的, 我也没有进行过进一步的考究.不过我想, 不管是不是这样的, 假如遇到这种情况的话, 我们该怎么做.如何把对象序列化为这样的 JSON 格式.当然可以简单粗暴地将就将字段直接声明为 storage
活着 editor
, 是一种办法, 就是不够好.接下来就看看如何使用 Jackson 实现这样的情况:
Jackson 动态生成 key 自定义注解 首选我们自定义一个注解, 就是说如果 Jackson 在解析的时候如果遇到这样的注解, 使用我们自己的逻辑进行处理.
1 2 3 4 5 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface RepresentationType { }
在需要进行动态生成 key 的字段上加上此注解:
1 2 3 4 5 6 7 8 @Data public class WikiContentBody { /** * 根据不同的 Representation 生成对应的 key */ @RepresentationType private WikiContentRepresentation contentRepresentation; }
实现自定义的 JacksonAnnotationIntrospector 这一步需要继承 JacksonAnnotationIntrospector
类, 并覆盖 isAnnotationBundle()
、findNameForSerialization()
等方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /** * 动态替换 WikiContentBody#contentRepresentation 在序列化过程中的名称. */ public class RepresentationTypeSerializer extends JacksonAnnotationIntrospector implements Versioned { @Override public boolean isAnnotationBundle(Annotation ann) { return false; } @Override public PropertyName findNameForSerialization(Annotated a) { return null; } }
将 JacksonAnnotationIntrospector 设置到 Jackson 序列化过程中 要让 Jackson 使用我们自定义的 RepresentationTypeSerializer
实现, 简单的将其设置到 ObjectMapper
中即可:
1 2 3 4 ObjectMapper objectMapper = new ObjectMapper(); AnnotationIntrospector representationTypeSerializer = new RepresentationTypeSerializer(); // 使用我们自定义的 AnnotationIntrospector objectMapper.setAnnotationIntrospector(representationTypeSerializer);
RepresentationTypeSerializer 的具体实现 上面我们只是稍微简单的说明了下 RepresentationTypeSerializer
需要覆盖哪些方法, 并没有给出具体的实现.首先是 isAnnotationBundle()
方法, 这个方法毕竟简单, 具体实现如下:
1 2 3 4 5 6 @Override public boolean isAnnotationBundle(Annotation ann) { Class<?> cls = ann.annotationType(); // 是否可以处理 RepresentationType 注解 return RepresentationType.class == cls || super.isAnnotationBundle(ann); }
接下来是 findNameForSerialization()
方法.先看下此方法的签名:
1 2 public PropertyName findNameForSerialization(Annotated a) { }
方法中只有一个参数 Annotated
, 那我们如何知道怎么进行替换呢? 替换成什么值呢? Annotated
似乎并没有提供什么有用的方法, 没法取到 WikiContentBody
中的 contentRepresentation
字段值.
利用 ThreadLocal 取得动态动态 key 刚刚问题就卡在了 findNameForSerialization(Annotated a)
方法没有办法取得动态的 key.只要解决了这个问题, 那么一切都可以顺利进行了.
这个时候就得搬出 ThreadLocal
.也就是说在序列化之前将要动态生成的 key 放到 ThreadLocal
里.看下具体的实现.
首先是 ThreadLocal
的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class RepresentationTypeThreadLocal { private static ThreadLocal<ContentRepresentation> CR_THREAD_LOCAL = new ThreadLocal<>(); public static void set(ContentRepresentation representation) { CR_THREAD_LOCAL.set(representation); } public static ContentRepresentation get() { return CR_THREAD_LOCAL.get(); } public static void remove() { CR_THREAD_LOCAL.remove(); } }
接下来是 findNameForSerialization()
方法的具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Override public PropertyName findNameForSerialization(Annotated a) { RepresentationType type = a.getAnnotation(RepresentationType.class); if (type != null) { String name = getPropertyName(); if (name != null) { return PropertyName.construct(name); } } return super.findNameForSerialization(a); } private String getPropertyName() { // 利用 ThreadLocal 取得动态 key ContentRepresentation representation = RepresentationTypeThreadLocal.get(); if (representation != null) { return representation.getRepresentation(); } ... }
在序列化之前首先先调用类似于如下的代码:
1 RepresentationTypeThreadLocal.set(ContentRepresentation.EDITOR);
测试打印结果如下: